package be.js.sqlCodeGenerator;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * Opens an sql template file for a given file name. The template data
 * is stored in templateSQL.
 * Contains helper methods for generating and extracting
 * data from variables
 * Abstract class. Any class extending it must implement the 
 * generate() method.
 * 
 * $Id: SQLTemplate.java 81 2013-03-12 21:15:39Z johan.schamp@gmail.com $ 
 * @author johan schamp (johan.schamp@gmail.com)
 * 
 */
public abstract class SQLTemplate { 

	protected String templateName;
	protected String tableName;
	protected List<String> columns;
	protected String generatedFilePrefix;
	
	private String templateSQL = "";
	private String generatedSQL = "";
	
	protected boolean preserveCRLF = true;

	/**
	 * Constructor. Opens the template file and stores its content in the 'templateSQL' field.
	 * 
	 * @param templateName  an SQL template file name that is present in the 'templates' folder
	 * @param tableName a table name
	 * @param columns a list of columns for the table
	 * @param generatedFilePrefix a prefix for the generated files in the 'generated' folder
	 * @throws IOException
	 */
	public SQLTemplate(String templateName, String tableName, List<String> columns, String generatedFilePrefix) throws IOException {
		this.templateName = templateName;
		this.tableName = tableName; 
		this.columns = columns;
		this.generatedFilePrefix = generatedFilePrefix;
		
		String line;

		try{
			File f =  new File(templateName); 
			BufferedReader br = new BufferedReader(new FileReader(f));
			while ((line = br.readLine()) != null) { // while loop begins here
				templateSQL += line;
				if (preserveCRLF){
					templateSQL += SQLCodeGenerator.CRLF;
				}
			} // end while 
		} catch (IOException e){
			throw new IOException(e);
		}
		
		// all generations should occur on the generatedSQL variable 
		generatedSQL = templateSQL; 
	}

	/***************************** getters and seters *********************************/
	/**
	 * @return the preserveCRLF
	 */
	public boolean isPreserveCRLF() {
		return preserveCRLF;
	}

	/**
	 * @param preserveCRLF the preserveCRLF to set
	 */
	public void setPreserveCRLF(boolean preserveCRLF) {
		this.preserveCRLF = preserveCRLF;
	}
	
	/**
	 * @return the templateName
	 */
	public final String getTemplateName() {
		return templateName;
	}

	/**
	 * @return the template sql
	 */
	public final String getTemplateSQL() {
		return templateSQL;
	}

	/**
	 * @return the generated sql
	 */
	public final String getGeneratedSQL() {
		return generatedSQL;
	}

	/***************************** business *********************************/
	
	/**
	 * This procedure must be implemented.
	 * It should generate the code and make it available
	 * in the generatedSQL variable (accessible thru getter).
	 * Input data is thus stored in templateSQL.
	 * After code generation, output data is available in generatedSQL field.
	 */
	public abstract void generate() throws IOException; 

	/**
	 * Replaces variables in the sqlTemplate string with values.
	 * Variables and their values are stored in an HashMap
	 * given as parameter.
	 *  
	 * @param replacements the replacements in a HashMap structure
	 */
	protected void replaceVariables(HashMap<String, String> replacements) {       
        // set the generatedSQL
        generatedSQL = replaceVariables(generatedSQL, replacements);
    }
	
	/**
	 * Replaces variables in the given string with values.
	 * Variables and their values are stored in an HashMap
	 * given as parameter.
	 * Variables in a template file have the following format: {variableName}.
	 *  
	 * @param text the text to be replaced
	 * @param replacements the replacements in a HashMap structure
	 * @return the string replaced with the variables 
	 */
	protected String replaceVariables(String text, HashMap<String, String> replacements) {
        Pattern pattern = Pattern.compile("\\{(.+?)\\}");
        Matcher matcher = pattern.matcher(text);

        StringBuilder builder = new StringBuilder();
        int i = 0;
        while (matcher.find()) {
            String replacement = replacements.get(matcher.group(1));
            builder.append(text.substring(i, matcher.start()));
            if (replacement == null) {
                builder.append("");
                i = matcher.start();
            } else {
                builder.append(replacement);
                i = matcher.end();
            }
        }
        builder.append(text.substring(i, text.length()));
        
        // set the generatedSQL
        return builder.toString();
    }
	
	/**
	 * Gets everything between the 2 variable placeholders 
	 * If only 1 variable is found, everything between the
	 * variable and the end is returned. If no variable is found,
	 * then an empty string is returned
	 *  
	 * @param variable the variable name for the placeholders
	 */
	protected String getBetweenVariable(String variable) {
		String between = "";
		String v = "{" + variable + "}";
		int r;
		int l = generatedSQL.indexOf(v);
		
		if (l > -1){
			l += variable.length() + 2;
			r = generatedSQL.indexOf(v, l);
			if (r > -1){
				between = generatedSQL.substring(l, r);
			} else {
				// only 1 variable, get the string till the end
				between = generatedSQL.substring(l);
			}
		}
		return between;
    }
	
	/**
	 * Replaces everything between the variable placeholders with a
	 * given replacement.
	 * If only 1 variable is found, everything between the
	 * variable and the end is replaced. If no variable is found,
	 * then nothing is replaced.
	 *  
	 * @param variable the variable name for the placeholders
	 * @param replacements the replacement value
	 */
	protected void replaceBetweenVariable(String variable, String replacement) {
		String result = "";
		String v = "{" + variable + "}";
		int r;
		int l = generatedSQL.indexOf(v);
		if (l > -1){
			result = generatedSQL.substring(0, l);
			r = generatedSQL.indexOf(v, l + variable.length() + 2);
			if (r > -1){
				r += variable.length() + 2;
				result += replacement + generatedSQL.substring(r);
			} else {
				// only 1 variable, replace everything till the end
				result += replacement;
			}
			generatedSQL = result;
		}
    }
	
	/**
	 * Saves the generatedSQL string to a file 
	 * in the directory GENRATEDDIR with the
	 * given fileName
	 * 
	 * @param fileName the file name of the sql should be saved 
	 */
	protected void saveGeneratedSQL(String fileName) throws IOException { 		
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(fileName));
			bw.write(generatedSQL);
			bw.close();
		} catch (IOException e){
			throw new IOException(e);
		}
	}


}
